package marco.android.CouchControl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.Vector;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import marco.android.CouchControl.TouchPadPanel.TouchPadToSurfaceListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.opengl.GLUtils;
import android.opengl.GLSurfaceView.Renderer;

public class GLRendererThread implements Renderer, TouchPadToSurfaceListener
{
	private Square mSquare = null;
	//private SmoothSquare mSmoothSq;
	//private Cube mCube;
	//private Group mGroup;

	private float mSurfaceW = 2;
	private float mSurfaceH = 2;
	private float mLeft = -1;
	private float mRight = 1;
	private float mTop = 1;
	private float mBottom = -1;
	private float mGlW = 2;
	private float mGlH = 2;
	private float mScaleFactor = 1;
	private float mFocusX = 0;
	private float mFocusY = 0;
	private boolean mParticlesStop = true;
	private Vector<Particle> mParticles = new Vector<Particle>();
	private float mX, mY;
	private static final int PARTICLE_COUNT = 100;
	private float mDt;
	private float [] mGravity = { 0f, 0f, -9.81f };

	public GLRendererThread(Context c, float dt)
	{
		//mCube = new Cube(1, 1, 1);
		//mCube.rx = 45;
		//mCube.ry = 45;
		//mContext = c;
		//			mSquare = new Square(-getWidth() / 2f, -getHeight() / 2f, getWidth(), getHeight());
		//mSmoothSq = new SmoothSquare();
		//mGroup = new Group();
		//mGroup.add(mCube);
		
		mDt = dt;
	}
	
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config)
	{
		// Load the texture for the square
		//			mSquare.loadGLTexture(gl, mContext);
		//			gl.glShadeModel(GL10.GL_SMOOTH); 			//Enable Smooth Shading
		//			gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f); 	//Black Background
		//			gl.glClearDepthf(1.0f); 					//Depth Buffer Setup
		//			gl.glEnable(GL10.GL_DEPTH_TEST); 			//Enables Depth Testing
		//			gl.glDepthFunc(GL10.GL_LEQUAL); 			//The Type Of Depth Testing To Do
		//
		//			//Really Nice Perspective Calculations
		//			gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);



		// Set the background color to black ( rgba ).
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
		// Enable Smooth Shading, default not really needed
		gl.glShadeModel(GL10.GL_SMOOTH);
		// Depth buffer setup.
		gl.glClearDepthf(1.0f);
		// Enables depth testing.
		gl.glDisable(GL10.GL_DEPTH_TEST);
		// The type of depth testing to do.
		//gl.glDepthFunc(GL10.GL_LEQUAL);
		// Really nice perspective calculations.
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
				GL10.GL_NICEST);
	}
	
	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height)
	{
		if(height == 0)
		{ 						//Prevent A Divide By Zero By
			height = 1; 		//Making Height Equal One
		}

		mSurfaceW = width;
		mSurfaceH = height;

		gl.glViewport(0, 0, width, height); 	//Reset The Current Viewport
		gl.glMatrixMode(GL10.GL_PROJECTION); 	//Select The Projection Matrix
		gl.glLoadIdentity(); 					//Reset The Projection Matrix

//		float ratio = (float)width / height;
//		mLeft = -ratio;
//		mRight = ratio;
//		mTop = 1;
//		mBottom = -1;
		//Calculate The Aspect Ratio Of The Window
		//GLU.gluPerspective(gl, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
		//gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
		
		// GL coordinate bounds with origin at center of viewport
		mRight = mSurfaceW / 2;
		mLeft = -mRight;
		mTop = mSurfaceH / 2;
		mBottom = -mTop;
		gl.glOrthof(mLeft, mRight, mBottom, mTop, -1, 1);
		
		mGlW = mRight - mLeft;
		mGlH = mTop - mBottom;

		gl.glMatrixMode(GL10.GL_MODELVIEW); 	//Select The Modelview Matrix
		gl.glLoadIdentity(); 

		mSquare = new Square(mLeft, mBottom, mSurfaceW, mSurfaceH);
	}
	
	@Override
	public void onDrawFrame(GL10 gl)
	{
		// clear Screen and Depth Buffer
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
		// Reset the Modelview Matrix
		gl.glLoadIdentity();
		
		// Scale from zoom control
		gl.glTranslatef(mFocusX, mFocusY, 0f);
		gl.glScalef(mScaleFactor, mScaleFactor, 1f);
		
		
		//GLU.gluLookAt(gl, 0, 0, -5, 0, 0, 0, 0, 1, 0);
		//gl.glTranslatef(0, 0, -4f);

		DoFling();
		// Draw stuffs
		if(mSquare != null)
		{
			if(fling == false)
			{
			   gl.glColor4f(mRed, mGreen, mBlue, 1.f);
			}
			else
			{
				gl.glColor4f(1f, 1f, 1f, 1f);
			}
			DrawBox(gl);
		}
		DrawPointer(gl);
		UpdateParticles(gl);
		//gl.glTranslatef(0, -3, 0);
		//mSmoothSq.draw(gl);
		//mCube.draw(gl);
		//mGroup.draw(gl);
	}

	public void setColor(float r, float g, float b)
	{
		mRed = r;
		mGreen = g;
		mBlue = b;
	}

	private float mRed;
	private float mGreen;
	private float mBlue;

	private void DrawBox(GL10 gl)
	{
		mSquare.draw(gl);
	}

	private void DrawPointer(GL10 gl)
	{
		FloatBuffer vertexBuffer;
		ByteBuffer vbb = ByteBuffer.allocateDirect(3 * 4);
		vbb.order(ByteOrder.nativeOrder());
		vertexBuffer = vbb.asFloatBuffer();
		
		float [] vertex = { mX, mY, 0f };
		vertexBuffer.put(vertex);
		gl.glPointSize(8);
		gl.glColor4f(0f, 1f, 1f, 0.8f);
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		vertexBuffer.position(0);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
		
		gl.glDrawArrays(GL10.GL_POINTS, 0, 1);
		
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
	}
	
	private void UpdateParticles(GL10 gl)
	{
		FloatBuffer vertexBuffer;
		int coordCount = 3;
		int colorCount = 4;
		int floatsPerVertex = coordCount + colorCount;
		int stride = floatsPerVertex * 4;

		ByteBuffer vbb = ByteBuffer.allocateDirect(mParticles.size() * stride);
		vbb.order(ByteOrder.nativeOrder());
		vertexBuffer = vbb.asFloatBuffer();

		for(int i = 0; i < mParticles.size(); ++i)
		{
			if(mParticles.get(i).Update(mGravity) == true)
			{
				// Particle expired, remove from surface.
				mParticles.remove(i);
				if(mParticlesStop == false)
				{
					// Screen is still being touched, replace the expired particle.
					mParticles.add(new Particle(mX, mY, mBottom, mTop,
											    mLeft, mRight, mDt));
				}
			}
			else
			{
				Particle p = mParticles.get(i);
				float [] vertex = { p.GetX(), p.GetY(), 0.f};
				vertexBuffer.put(vertex);
				
				float [] color = p.GetColorRGBA(); 
				vertexBuffer.put(color);
				//c.drawPoint(p.GetX(), p.GetY(), p.GetPaint());
				//					int w = mTextureW / 20;
				//					int h = mTextureH / 20;
				//					int x = (int)(p.GetX() + (w / 2.f));
				//					int y = (int)(p.GetY() - (h / 2.f));
				//					Rect r = new Rect(x, y, x+w, y+h);
				//					c.save();
				//					c.rotate(p.GetBodyRotation(), p.GetX(), p.GetY());
				//					c.drawBitmap(mTexture, null, r, p.GetPaint());
				//					c.restore();
			}
		}

		int count = vertexBuffer.position() / floatsPerVertex;

		if(count > 0)
		{
			gl.glPointSize(6);
			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
			gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
			vertexBuffer.position(0);
			gl.glVertexPointer(coordCount, GL10.GL_FLOAT, stride, vertexBuffer);
			vertexBuffer.position(coordCount);
			gl.glColorPointer(colorCount, GL10.GL_FLOAT, stride, vertexBuffer);

			gl.glDrawArrays(GL10.GL_POINTS, 0, count);
			gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
			gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
		}
	}

	private float ScreenToGLCoordinateX(float x)
	{
		return(((x / mSurfaceW) * mGlW) - (mGlW / 2f));
	}

	private float ScreenToGLCoordinateY(float y)
	{
		return(-(((y / mSurfaceH) * mGlH) - (mGlH / 2f)));
	}

	private float ScreenToGLCoordinateDx(float dx)
	{
		return(dx * mGlW / mSurfaceW);
	}

	private float ScreenToGLCoordinateDy(float dy)
	{
		return(-dy * mGlH / mSurfaceH);
	}
	
	@Override
	public void onDown(float x, float y)
	{
		// Update mX and mY inside the runnable so that they
		// are modified from within the GL thread and there
		// is no risk of a race condition.
		mX = ScreenToGLCoordinateX(x);
		mY = ScreenToGLCoordinateY(y);
		mParticlesStop = false;
		int j = (mParticles.size() == 0 ? PARTICLE_COUNT : PARTICLE_COUNT - mParticles.size());
		for(int i = 0; i < j; ++i)
		{
			mParticles.add(new Particle(mX, mY, mBottom, mTop,
										mLeft, mRight, mDt));
		}
		setColor(mX / mGlW,
				 mY / mGlH, 1.0f);
	}

	@Override
	public boolean isWithinBounds(float x, float y, float z)
	{
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void onDrag(float dx, float dy)
	{
		// Update mX and mY inside the runnable so that they
		// are modified from within the GL thread and there
		// is no risk of a race condition.
		mX += ScreenToGLCoordinateDx(dx);
		mY += ScreenToGLCoordinateDy(dy);
		if(mX > mRight || mX < mLeft ||
		   mY > mTop || mY < mBottom)
		{
			mX = Math.max(Math.min(mX, mRight), mLeft);
			mY = Math.max(Math.min(mY, mTop), mBottom);
		}
	}

	@Override
	public void onScale(float scaleFactor, float focusX, float focusY)
	{
		mScaleFactor *= scaleFactor;

		// Don't let the object get too small or too large.
		mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, 5.0f));
		mFocusX = ScreenToGLCoordinateX(focusX);
		mFocusY = ScreenToGLCoordinateY(focusY);
	}

	@Override
	public void onUp(float x, float y)
	{
		if(fling == false)
		{
		   // do not interrupt the particle
		   // animation until the fling gesture
		   // is complete.
		   mParticlesStop = true;
		}
	}

	@Override
	public void onDoubleTap(float x, float y)
	{
		// TODO Auto-generated method stub
		
	}

	private FlingHandler mFlinger = new FlingHandler();
	private boolean fling = false;
	@Override
	public void onFling(float dx, float dy, long durationMilliSecs)
	{
		// Start the fling
		fling = mFlinger.onFling(dx, dy, durationMilliSecs);
	}
	
	private void DoFling()
	{
		float [] pos = {0f, 0f};
		if(fling == true)
		{
			if(mFlinger.DoFling(pos) == true)
			{
				onDrag(pos[0], pos[1]);
			}
			else
			{
				mParticlesStop = true;
				fling = false;
			}
		}
	}

	@Override
	public void onLongPress(float x, float y)
	{
		// TODO Auto-generated method stub
		
	}
	
	public void onAcelerationChange(float[] gravity, float [] accel)
	{
		mGravity = gravity;
		onDrag(mGravity[0], mGravity[1]);
	}
	//==============================================================================================
	private class Square
	{
		protected FloatBuffer vertexBuffer;	// buffer holding the vertices

		protected float vertices[] = {
				-1.0f,  1.0f,  0.0f,		// V0 - bottom left
				-1.0f, -1.0f,  0.0f,		// V1 - top left
				1.0f, -1.0f,  0.0f,		// V2 - bottom right
				1.0f,  1.0f,  0.0f			// V3 - top right
		};

		protected ShortBuffer indexBuffer;
		// The order we wish to connect the vertices in
		protected short[] indices = { 0, 1, 2, 3 } ;//{ 0, 1, 2, 0, 2, 3 };

		public Square()
		{
			ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
			byteBuffer.order(ByteOrder.nativeOrder());
			vertexBuffer = byteBuffer.asFloatBuffer();
			vertexBuffer.put(vertices);
			vertexBuffer.position(0);

			// short is 2 bytes, therefore we multiply the number if
			// vertices with 2.
			ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
			ibb.order(ByteOrder.nativeOrder());
			indexBuffer = ibb.asShortBuffer();
			indexBuffer.put(indices);
			indexBuffer.position(0);
		}

		public Square(float x, float y, float w, float h)
		{
			float Vertices[] = {
					x,  y,  0.0f,		// V0 - bottom left
					x + w, y,  0.0f,		// V1 - top left
					x + w, y + h,  0.0f,		// V2 - bottom right
					x,  y + h,  0.0f };
			ByteBuffer byteBuffer = ByteBuffer.allocateDirect(Vertices.length * 4);
			byteBuffer.order(ByteOrder.nativeOrder());
			vertexBuffer = byteBuffer.asFloatBuffer();
			vertexBuffer.put(Vertices);
			vertexBuffer.position(0);

			// short is 2 bytes, therefore we multiply the number if
			// vertices with 2.
			ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
			ibb.order(ByteOrder.nativeOrder());
			indexBuffer = ibb.asShortBuffer();
			indexBuffer.put(indices);
			indexBuffer.position(0);
		}

		/** The draw method for the square with the GL context */
		public void draw(GL10 gl)
		{
			// Counter-clockwise winding.
			gl.glFrontFace(GL10.GL_CCW);
			// Enable face culling.
			gl.glEnable(GL10.GL_CULL_FACE);
			// What faces to remove with the face culling.
			gl.glCullFace(GL10.GL_BACK);

			// Enabled the vertices buffer for writing and to be used during
			// rendering.
			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
			// Specifies the location and data format of an array of vertex
			// coordinates to use when rendering.
			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

			gl.glDrawElements(GL10.GL_LINE_LOOP, indices.length,
					GL10.GL_UNSIGNED_SHORT, indexBuffer);

			// Disable the vertices buffer.
			gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
			// Disable face culling.
			gl.glDisable(GL10.GL_CULL_FACE);
		}


	}

//	private class Texture extends Square
//	{
//		private FloatBuffer textureBuffer;	// buffer holding the texture coordinates
//		private float texture[] = {
//				// Mapping coordinates for the vertices
//				0.0f, 1.0f,		// top left		(V2)
//				0.0f, 0.0f,		// bottom left	(V1)
//				1.0f, 1.0f,		// top right	(V4)
//				1.0f, 0.0f		// bottom right	(V3)
//		};
//
//		public Texture()
//		{
//			ByteBuffer byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
//			byteBuffer.order(ByteOrder.nativeOrder());
//			textureBuffer = byteBuffer.asFloatBuffer();
//			textureBuffer.put(texture);
//			textureBuffer.position(0);
//		}
//
//		/** The texture pointer */
//		private int[] textures = new int[1];
//
//		public void loadGLTexture(GL10 gl, Context context)
//		{
//			// loading texture
//			Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),
//					R.drawable.bead_single_blue);
//
//			// generate one texture pointer
//			gl.glGenTextures(1, textures, 0);
//			// ...and bind it to our array
//			gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
//
//			// create nearest filtered texture scaled parameters
//			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
//			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//
//			// What to do when the image does not fit the vertices
//			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
//			gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
//
//			// Use Android GLUtils to specify a two-dimensional texture image from our bitmap
//			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//
//			// Clean up
//			bitmap.recycle();
//		}
//
//		@Override public void draw(GL10 gl)
//		{
//			gl.glEnable(GL10.GL_TEXTURE_2D);			//Enable Texture Mapping ( NEW )
//
//			// bind the previously generated texture
//			gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
//
//			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//			gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
//
//			// Set the face rotation
//			gl.glFrontFace(GL10.GL_CW);
//
//			// set the colour for the square
//			//gl.glColor4f(0.0f, 1.0f, 0.0f, 0.5f);
//
//			// Point to our vertex buffer
//			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
//			gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
//			// Draw the vertices as triangle strip
//			gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
//
//			//Disable the client state before leaving
//			gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//			gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
//
//			gl.glDisable(GL10.GL_TEXTURE_2D);
//		}
//	}
//
//	private class SmoothSquare extends Square
//	{
//		private FloatBuffer colorBuffer;
//
//		public SmoothSquare()
//		{
//			float[] colors =
//			{
//					1f, 0f, 0f, 1f, //v1
//					0f, 1f, 0f, 1f, //v2
//					0f, 0f, 1f, 1f, //v3
//					1f, 0f, 1f, 1f  //v4
//			};
//			// float has 4 bytes, colors (RGBA) * 4 bytes
//			ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
//			cbb.order(ByteOrder.nativeOrder());
//			colorBuffer = cbb.asFloatBuffer();
//			colorBuffer.put(colors);
//			colorBuffer.position(0);
//		}
//
//		@Override
//		public void draw(GL10 gl)
//		{
//			// Counter-clockwise winding.
//			gl.glFrontFace(GL10.GL_CCW);
//
//			// Enable face culling.
//			gl.glEnable(GL10.GL_CULL_FACE);
//
//			// What faces to remove with the face culling.
//			gl.glCullFace(GL10.GL_BACK);
//
//			// Enabled the vertices buffer for writing and to be used during
//			// rendering.
//			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//			// Specifies the location and data format of an array of vertex
//			// coordinates to use when rendering.
//			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
//
//			// Enable the color array buffer to be used during rendering.
//			gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
//			// Point out the where the color buffer is.
//			gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
//
//			gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);
//
//			// Disable the vertices buffer.
//			gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//			// Disable face culling.
//			gl.glDisable(GL10.GL_CULL_FACE);
//
//			// Disable the color buffer.
//			gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
//		}
//	}

}
